﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using lmsft.SDP;
using lmsft.RTP;
using System.Net;
using lmsft;
using lmsft.UPnP.NAT;

namespace lmsft_sip {
	public class nat_proc {

		NAT_TYPE nat_type;
		int m_StunServerPort;
		string m_StunServer;
		UPnP_NAT_Client m_pUPnP;


		/// <summary>
		/// Handles NAT and stores RTP data to <b>mediaStream</b>.
		/// </summary>
		/// <param name="mediaStream">SDP media stream.</param>
		/// <param name="rtpSession">RTP session.</param>
		/// <returns>Returns true if NAT handled ok, otherwise false.</returns>
		public bool nat_handle (SDP_MediaDescription mediaStream, RTP_Session rtpSession) {
			if (mediaStream == null) {
				throw new ArgumentNullException("mediaStream");
			}
			if (rtpSession == null) {
				throw new ArgumentNullException("rtpSession");
			}

			IPEndPoint rtpPublicEP = null;
			IPEndPoint rtcpPublicEP = null;

			// We have public IP.
			if (!Net_Utils.IsPrivateIP(rtpSession.LocalEP.IP)) {
				rtpPublicEP = rtpSession.LocalEP.RtpEP;
				rtcpPublicEP = rtpSession.LocalEP.RtcpEP;
			}
			else {
				switch (nat_type) {
					case NAT_TYPE.NONAT:
						rtpPublicEP = rtpSession.LocalEP.RtpEP;
						rtcpPublicEP = rtpSession.LocalEP.RtcpEP;
						break;
					case NAT_TYPE.STUN:
						rtpSession.StunPublicEndPoints(m_StunServer, m_StunServerPort, out rtpPublicEP, out rtcpPublicEP);
						break;
					case NAT_TYPE.UPNP:
						if (!m_pUPnP.IsSupported)
							break;
						var rtpPublicPort = rtpSession.LocalEP.RtpEP.Port;
						var rtcpPublicPort = rtpSession.LocalEP.RtcpEP.Port;

						try {
							var maps = m_pUPnP.GetPortMappings();
							while (true) {
								var conficts = false;
								// Check that some other application doesn't use that port.
								foreach (var map in maps) {
									// Existing map entry conflicts.
									if (Convert.ToInt32(map.ExternalPort) == rtpPublicPort || Convert.ToInt32(map.ExternalPort) == rtcpPublicPort) {
										rtpPublicPort += 2;
										rtcpPublicPort += 2;
										conficts = true;

										break;
									}
								}
								if (!conficts) {
									break;
								}
							}

							m_pUPnP.AddPortMapping(true, "LS RTP", "UDP", null, rtpPublicPort, rtpSession.LocalEP.RtpEP, 0);
							m_pUPnP.AddPortMapping(true, "LS RTCP", "UDP", null, rtcpPublicPort, rtpSession.LocalEP.RtcpEP, 0);

							var publicIP = m_pUPnP.GetExternalIPAddress();

							rtpPublicEP = new IPEndPoint(publicIP, rtpPublicPort);
							rtcpPublicEP = new IPEndPoint(publicIP, rtcpPublicPort);

							mediaStream.Tags.Add("upnp_rtp_map", new UPnP_NAT_Map(true, "UDP", "", rtpPublicPort.ToString(), rtpSession.LocalEP.IP.ToString(), rtpSession.LocalEP.RtpEP.Port, "LS RTP", 0));
							mediaStream.Tags.Add("upnp_rtcp_map", new UPnP_NAT_Map(true, "UDP", "", rtcpPublicPort.ToString(), rtpSession.LocalEP.IP.ToString(), rtpSession.LocalEP.RtcpEP.Port, "LS RTCP", 0));
						}
						catch {

						}
						break;
					default:
						break;
				}
			}

			if (rtpPublicEP != null && rtcpPublicEP != null) {
				mediaStream.Port = rtpPublicEP.Port;
				if ((rtpPublicEP.Port + 1) != rtcpPublicEP.Port) {
					// Remove old rport attribute, if any.
					for (var i = 0; i < mediaStream.Attributes.Count; i++) {
						if (string.Equals(mediaStream.Attributes[i].Name, "rport", StringComparison.InvariantCultureIgnoreCase)) {
							mediaStream.Attributes.RemoveAt(i);
							i--;
						}
					}
					mediaStream.Attributes.Add(new SDP_Attribute("rport", rtcpPublicEP.Port.ToString()));
				}
				mediaStream.Connection = new SDP_Connection("IN", "IP4", rtpPublicEP.Address.ToString());

				return true;
			}

			return false;
		}

	}
}
